<?php
/**
 * Author:xsc
 * Time:2023/11/21 11:11
 */

namespace App\Extensions\Common;

use App\Extensions\BaseService;
use App\Common\ResponseCode;
use App\Exceptions\BusinessException;
use App\Jobs\ApplylogJob;
use App\Traits\HttpHelper;
use Illuminate\Support\Facades\Log;

class SandPayService extends BaseService
{

    use HttpHelper;

    private $url;

    private $public_key_path;

    private $private_key_path;

    private $private_key_password;

    private $access_mid;

    public function __construct()
    {
        if (env('APP_ENV') == 'pro') {
            $app_env = 'pro';
        } else {
            $app_env = 'test';
        }
        $this->public_key_path = base_path() . '/keys/sand/' . $app_env . '_public.cer';
        $this->private_key_path = base_path() . '/keys/sand/' . $app_env . '_private.pfx';
        $this->url = env('SAND_PAY_URL');
        $this->private_key_password = env('SAND_PAY_PRIVATE_KEY_PASSWORD');
        $this->access_mid = env('SAND_PAY_ACCESS_MID');
    }

    // test
    public function accountMemberQuery()
    {
        $url = 'v4/sd-wallet/api/m-wallet/account.member.query';

        $params = [
            'accessMid' => '6888802001560',
            'bizData'   => [
                "mid"       => "6888806037286",
                "bizUserNo" => "ccvip009",
            ],
        ];

        $res = $this->request($url, $params);

        if ($res && $res['resultStatus'] == 'success') {

        }
        return $res;
    }

    // test
    public function h5Access(string $accessMid, string $phone)
    {
        $url = 'v4/sd-portal/api/psdb/psdb.h5.access';

        $params = [
            'accessMid' => $accessMid,
            'bizData'   => [
                "phone" => $phone,
            ],
        ];

        $res = $this->request($url, $params);

        $url = null;
        if ($res && $res['resultStatus'] == 'success') {
            $url = $res['url'];
        }
        return $url;
    }

    /**
     * 请求
     * @param string $url
     * @param array $params
     * @return mixed
     * @throws BusinessException
     * Author:xsc
     * Time:2023/11/21 17:31
     */
    private function request(string $url, array $params)
    {
        $data = [
            'accessMid'   => $this->access_mid,
            'timestamp'   => date('Y-m-d H:i:s'),
            'version'     => '1.0.0',
            'signType'    => 'RSA',
            //'sign'        => '',
            'encryptType' => 'AES',//AES加密或者 NONE 不加密
            //'encryptKey'  => '',
            'bizData'     => $params['bizData'],
        ];

        Log::info('[杉德宝请求参数]' . json_encode($data['bizData'], 256));

        $public_key = $this->loadX509Cert($this->public_key_path);

        $private_key = $this->loadPk12Cert($this->private_key_path, $this->private_key_password);

        // 1.生成aes_key
        $aes_key = $this->getAesKey();

        // 2.用aes_key加密bizData
        $data['bizData'] = $this->aesEncrypt($data['bizData'], $aes_key);

        // 3.用公钥加密aes_key
        $data['encryptKey'] = $this->rsaEncrypt($aes_key, $public_key);

        // 4.用私钥生成sign
        $data['sign'] = $this->getSign($data['bizData'], $private_key);

        if (!strpos($url, '.com.cn')) {
            $url = $this->url . $url;
        }

        //Log::info('[杉德宝请求参数]' . json_encode($data, 256));

        try {
            $res = $this->sendPost($url, $data)->getBody()->getContents();
        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
            throw new BusinessException(ResponseCode::SAND_PAY_REQUEST_ERROR);
        }

        if (!$res) {
            throw new BusinessException(ResponseCode::SAND_PAY_REQUEST_ERROR);
        }

        //Log::info('[杉德宝返回数据]' . $res);

        // 异步记录请求日志
        $log = [
            'action'      => $url,
            'apply_info'  => json_encode($data, 256),
            'result_info' => $res,
            'create_time' => date('Y-m-d H:i:s'),
            'type'        => '杉德宝'
        ];
        dispatch(new ApplylogJob($log));

        $res = json_decode($res, true);

        if ($res['respCode'] == 'success') {
            // 5.用公钥验签
            $this->checkSign($res['bizData'], $res['sign'], $public_key);

            // 6.用私钥解密得到aes_key
            $decrypt_aes_key = $this->rsaDecrypt($res['encryptKey'], $private_key);

            // 7.用解密后的aes_key解密bizData得到响应体
            $res['bizData'] = json_decode($this->aesDecrypt($res['bizData'], $decrypt_aes_key), true);

            Log::info('[杉德宝返回数据]' . json_encode($res['bizData'], 256));
        }

        return $res['bizData'];
    }

    /**
     * 获取公钥
     * @param string $path
     * @return mixed
     * @throws \Exception
     * Author:xsc
     * Time:2023/11/21 17:47
     */
    private function loadX509Cert(string $path)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new \Exception('loadx509Cert::file_get_contents ERROR');
            }
            $cert = chunk_split(base64_encode($file), 64, "\n");
            $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";
            $res = openssl_pkey_get_public($cert);
            $detail = openssl_pkey_get_details($res);
            openssl_free_key($res);

            if (!$detail) {
                throw new \Exception('loadX509Cert::openssl_pkey_get_details ERROR');
            }
            return $detail['key'];
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * 获取私钥
     * @param string $path
     * @param string $password
     * @return mixed
     * @throws \Exception
     * Author:xsc
     * Time:2023/11/21 17:47
     */
    private function loadPk12Cert(string $path, string $password)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new \Exception('loadPk12Cert::file_get_contents ERROR');
            }

            if (!openssl_pkcs12_read($file, $cert, $password)) {
                throw new \Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
            }
            return $cert['pkey'];
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * 生成一个随机key
     * @param int $length
     * @return false|string
     * Author:xsc
     * Time:2023/11/21 17:47
     */
    private function getAesKey(int $length = 16)
    {
        $token = '';

        $token_length = round($length * 4 / 3);

        for ($i = 0; $i < $token_length; ++$i) {
            $token .= chr(rand(32, 1024));
        }

        $token = base64_encode(str_shuffle($token));

        return substr($token, 0, $length);
    }

    /**
     * aes加密
     * @param array $data
     * @param string $aes_key
     * @return string
     * Author:xsc
     * Time:2023/11/21 17:46
     */
    private function aesEncrypt(array $data, string $aes_key)
    {
        ksort($data);

        $encrypt_key = openssl_encrypt(json_encode($data, 256), 'AES-128-ECB', $aes_key, OPENSSL_RAW_DATA);

        return base64_encode($encrypt_key);
    }

    /**
     * aes解密
     * @param string $data
     * @param string $aes_key
     * @return false|string
     * Author:xsc
     * Time:2023/11/21 17:46
     */
    private function aesDecrypt(string $data, string $aes_key)
    {
        return openssl_decrypt(base64_decode($data), 'AES-128-ECB', $aes_key, OPENSSL_RAW_DATA);
    }

    /**
     * 用公钥对aes_key进行rsa加密
     * @param string $aes_key
     * @param string $public_key
     * @return string
     * Author:xsc
     * Time:2023/11/21 17:46
     */
    private function rsaEncrypt(string $aes_key, string $public_key)
    {
        openssl_public_encrypt($aes_key, $encrypted_data, $public_key, OPENSSL_PKCS1_PADDING);

        return base64_encode($encrypted_data);
    }

    /**
     * 用私钥解密得到aes_key
     * @param string $encrypted_data
     * @param string $private_key
     * @return string
     * Author:xsc
     * Time:2023/11/21 17:46
     */
    private function rsaDecrypt(string $encrypted_data, string $private_key)
    {
        openssl_private_decrypt(base64_decode($encrypted_data), $decrypted_data, $private_key, OPENSSL_PKCS1_PADDING);

        return $decrypted_data;
    }

    /**
     * 用私钥生成签名
     * @param string $data
     * @param string $private_key
     * @return string
     * Author:xsc
     * Time:2023/11/21 17:46
     */
    private function getSign(string $data, string $private_key)
    {
        $resource = openssl_pkey_get_private($private_key);
        openssl_sign($data, $sign, $resource, OPENSSL_ALGO_SHA256);
        openssl_free_key($resource);
        return base64_encode($sign);
    }

    /**
     * 用公钥验签
     * @param string $data
     * @param string $sign
     * @param string $public_key
     * @return int
     * @throws \Exception
     * Author:xsc
     * Time:2023/11/21 17:45
     */
    private function checkSign(string $data, string $sign, string $public_key)
    {
        $resource = openssl_pkey_get_public($public_key);
        $result = openssl_verify($data, base64_decode($sign), $resource, OPENSSL_ALGO_SHA256);
        openssl_free_key($resource);

        if ($result != 1) {
            throw new \Exception('签名验证未通过,data:' . $data . ',sign:' . $sign);
        }
        return $result;
    }

}
